Код и жизнь. Графики всего подряд
Импортируем файл с историей
path = "result.json";
history = Import[path, "RawJSON"]["messages"];
Изучаем структуру данных
Length @ history
history[[1]]
history[[9]]
Message9 =
StringJoin @
Map[If[StringQ[#], #, #["text"]]&] @
If[StringQ[#], {#}, #]& @
history[[9, "text"]]
Reactions3 = Values @ history[[3, "reactions", All, {"emoji", "count"}]]
.wlx
<Grid><Reactions3 /></Grid>
Виды графиков
Show[
Plot[{Sin[x], BesselJ[0, x]}, {x, 0, 15},
ImageSize -> 600,
Frame -> True,
GridLines -> Automatic
],
ListPlot[Table[{x, Cos[x]}, {x, 0, 15, 0.1}],
PlotStyle -> Directive[Darker[Green], PointSize[0.025]]
]
]
Чарты
PieChart[RandomInteger[10, 5]]
Histogram[Map[Total] @ RandomReal[1, {1000, 1000}],
ColorFunction -> "TemperatureMap",
ImageSize -> {600, 400},
Frame -> True,
GridLines -> Automatic
]
Контуры
ContourPlot[Sin[x] * Cos[y], {x, -2, 2}, {y, -2, 2},
ImageSize -> {500, 500}
]
3D
Plot3D[Sin[x] * Cos[y], {x, -2, 2}, {y, -2, 2},
ImageSize -> {600, 600},
ColorFunction -> "TemperatureMap"
]
Графики группы!
ChatReport = <||>;
Общая активность в группе
activityTimeline =
MapIndexed[{#, #2[[1]]}&] @
Map[FromUnixTime] @
Map[ToExpression] @
Query[All, "date_unixtime"] @
Select[KeyExistsQ[#, "date_unixtime"]&] @
history;
ChatReport["Общая активность в группе"] =
DateListPlot[{activityTimeline},
ImageSize -> {600, 400},
PlotStyle -> Red,
Frame -> True
]
ChatReport["Общая активность в группе по дням"] =
DateHistogram[activityTimeline[[All, 1]], "Day",
ImageSize -> {600, 400},
ColorFunction -> "TemperatureMap",
DateTicksFormat -> {"Month", ".", "Day"},
FrameTicks -> {
DateRange[DateObject[{2025, 4, 7}], Today, Quantity[2, "Days"]],
Range[0, 500, 50]},
Frame -> True
]
Приглашения
invatesHistory =
Query[All, {"date_unixtime" /* ToExpression /* FromUnixTime, "actor"}] @
Select[#action === "join_group_by_link"&] @
Select[#type === "service"&] @
history;
ChatReport["Как пользователи добавлялись в группу"] =
DateListPlot[
MapIndexed[{#1[[1]], #2[[1]]}&] @ invatesHistory,
ImageSize -> {600, 400}
]
Активность пользователей
messages =
Select[#type === "message"&] @
history;
writers =
SortBy[Last] @
Tally @
messages[[All, "from"]];
ChatReport["Самые активные участники"] =
BarChart[writers[[-20 ;; , 2]],
ColorFunction -> "TemperatureMap",
ImageSize -> {800, 600},
Frame -> True,
BarOrigin -> Left,
ChartLabels -> writers[[-20 ;; , 1]],
PlotRange -> {{0, 1000}, {0, 21}},
GridLines -> Automatic
]
topWriter = writers[[-1, 1]];
topWritesMessages =
Map[FromUnixTime[ToExpression[#["date_unixtime"]]]&] @
Select[#from === topWriter&] @
messages;
ChatReport["Самые активный участник"] =
DateHistogram[topWritesMessages, "Day",
PlotLabel -> Style["Активность участника " <> topWriter, FontSize -> 20],
ImageSize -> {800, 400},
ColorFunction -> "Rainbow",
PlotRange -> {{AbsoluteTime[DateObject[{2025, 4, 6, 12, 0, 0}]], AbsoluteTime[Now]}, {0, 150}},
DateTicksFormat -> {"Month", ".", "Day"},
FrameTicks -> {
DateRange[DateObject[{2025, 4, 7}], Today, Quantity[2, "Days"]],
Range[0, 150, 10]},
Frame -> True
]
Связь пользователей
messagesAssoc =
Association @
Map[#id -> #&] @
messages;
replyGraph =
Map[#[[1]] -> #[[2]]&] @
Select[#[[1]] =!= #[[2]]&] @
Select[StringQ[#[[1]]] && StringQ[#[[2]]]&] @
DeleteDuplicates @
Map[Sort[{messagesAssoc[#id]["from"], messagesAssoc[#["reply_to_message_id"]]["from"]}]&] @
Select[KeyExistsQ[#, "reply_to_message_id"]&] @
messages;
.sh
npm i 3d-force-graph
.esm
import ForceGraph3D from '3d-force-graph';
core.ForceGraph3D = async (args, env) => {
//load shared library from WLJS Notebook store
await interpretate.shared.SpriteText.load();
//interprete input data
const data = await interpretate(args[0], env);
const SpriteText = interpretate.shared.SpriteText.SpriteText;
const opts = await core._getRules(args, env);
// Build labels mapping
const labels = (opts.VertexLabels || []).reduce((acc, { lhs, rhs }) => {
acc[lhs] = rhs;
return acc;
}, {});
// Collect node IDs and construct links
const nodeIds = new Set();
const links = data.map(({ lhs, rhs }) => {
nodeIds.add(lhs);
nodeIds.add(rhs);
return { source: String(rhs), target: String(lhs) };
});
// Create nodes with labels
const nodes = Array.from(nodeIds).map(id => ({
id: String(id),
label: labels[id] || String(id),
}));
let imageSize = (opts.ImageSize) || 350;
if (!Array.isArray(imageSize)) {
imageSize = [imageSize, imageSize * 0.7];
}
// Initialize the 3D force graph
const Graph = ForceGraph3D({})(env.element)
.width(imageSize[0])
.height(imageSize[1])
.cooldownTicks(100)
.graphData({ nodes, links })
.nodeThreeObject(node => {
const sprite = new SpriteText(node.label);
sprite.material.depthWrite = true; // Make sprite background transparent
sprite.color = 'white';
sprite.textHeight = 12;
return sprite;
})
.nodeThreeObjectExtend(false);
// Apply optional charge strength
if ('Charge' in opts) {
Graph.d3Force('charge').strength(opts.Charge);
}
Graph.onEngineStop(() => Graph.zoomToFit(400));
env.local.Graph = Graph;
};
core.ForceGraph3D.destroy = () => {
console.warn('3D graph was removed');
}
//make each instance unique
core.ForceGraph3D.virtual = true
ForceGraph3D /: MakeBoxes[f_ForceGraph3D, StandardForm] := With[{
(* compress it for the case if you have many labels and vertices *)
o = CreateFrontEndObject[f]
},
(* low-level decoration box *)
ViewBox[o, o]
];
ForceGraph3D /: MakeBoxes[f_ForceGraph3D, WLXForm] := With[{
(* compress it for the case if you have many labels and vertices *)
o = CreateFrontEndObject[f]
},
MakeBoxes[o, WLXForm]
];
ChatReport["Ответы друг другу"] =
ForceGraph3D[replyGraph,
ImageSize -> 1000
]
ApexCharts
.sh
npm i apexcharts --prefix .
.esm
import ApexCharts from 'apexcharts'
const whenVisible = (ele, cbk) => {
let observer = new IntersectionObserver(function(entries) {
if(entries[0].isIntersecting === true) {
observer.unobserve(ele);
cbk();
return;
}
}, { threshold: [0] });
observer.observe(ele);
};
core.ApexCharts = async (args, env) => {
const options = await interpretate(args[0], env);
const chart = new ApexCharts(env.element, options);
whenVisible(env.element, () => chart.render())
}
ApexCharts /: MakeBoxes[a: ApexCharts[_Association], StandardForm ] := With[{},
ViewBox[a, a]
]
ApexCharts /: MakeBoxes[a: ApexCharts[_Association], form: StandardForm ] := With[{o = CreateFrontEndObject[a]},
MakeBoxes[o, form]
] /; ByteCount[a] > 1024*4
ApexCharts /: MakeBoxes[a: ApexCharts[_Association], form: WLXForm ] := With[{o = CreateFrontEndObject[a]},
MakeBoxes[o, form]
]
Прямоугольные сектора
data =
Transpose @
Map[<|"x" -> #[[1]], "y" -> #[[2]]|>&] @
Tally @
messages[[All, "from"]];
ChatReport["Как у трейдеров"] =
ApexCharts[<|
"chart" -> <|"height" -> 500, "width"->800, "type" -> "treemap"|>,
"series" -> {
<|
"data" -> data
|>
},
"plotOptions" -> <|"treemap" -> <|"distributed" -> True|>|>
|>]
Паутина
weekActivity =
Tally @
Map[DateValue[#, "DayNameShort"]&] @
activityTimeline[[All, 1]]
ChatReport["Радиальная активность на паутине"] =
ApexCharts[<|
"chart" -> <|"height" -> 500, "width"->500, "type" -> "radar"|>,
"dataLabels" -> <|"enabled" -> True|>,
"xaxis" -> <|"categories" -> weekActivity[[All, 1]]|>,
"series" -> {
<|
"data" -> weekActivity[[All, 2]]
|>
}
|>]
Реакции
textBlocksToString[text_String] := text;
textBlocksToString[formatted_Association] := formatted["text"];
textBlocksToString[blocks_List] := StringJoin @ Map[textBlocksToString] @ blocks;
numberOfReactions[message_Association] :=
If[KeyExistsQ[message, "reactions"],
Total[message[["reactions", All, "count"]]],
(*Else*)
0
]
reactionsMetric =
SortBy[Last] @
Select[First[#] > 10&] @
Map[N[{Length[#], Mean[#[[All, "textLength"]]], Mean[#[[All, "numberOfReactions"]]]}]&] @
GroupBy[#from&] @
Map[<|
"from" -> #from,
"textLength" -> StringLength[textBlocksToString[#text]],
"numberOfReactions" -> numberOfReactions[#]|>&
] @
Select[KeyExistsQ[#, "reactions"]&] @
messages
ChatReport["Реакции - связь с числом сообщений и длиной"] =
ApexCharts[<|
"chart" -> <|"height" -> 500, "width"->900, "type" -> "bubble"|>,
"dataLabels" -> <|"enabled" -> False|>,
"fill" -> <|"opacity" -> 0.5|>,
"series" -> KeyValueMap[<|"name" -> #1, "data" -> {#2}|>&, reactionsMetric],
"xaxis" -> <|"min" -> -30, "max" -> 400|>,
"yaxis" -> <|"min" -> -100|>
|>]
Plotly
Снова реакции
totalReactions =
Query[-10 ;; ] @
Sort @
Map[Total[#[[All, "count"]]]&] @
GroupBy[#emoji&] @
Select[KeyExistsQ[#, "emoji"]&] @
Flatten @
Map[#reactions&] @
Select[KeyExistsQ[#, "reactions"]&] @
messages
ChatReport["Сектор-чарт для реакций"] =
Plotly[
<|
"values" -> Values[totalReactions],
"labels" -> Keys[totalReactions],
"type" -> "pie",
"hole" -> 0.5,
"title" -> <|
"text" -> "Реакции",
"font" -> <|"size" -> 24|>
|>,
"textinfo" -> "label+percent"
|>,
<|
"height" -> 600,
"width" -> 600,
"paper_bgcolor" -> "#FFF0"
|>]
Слайды
.wlx
ManySlides[Rule["Data", assoc_Association]] :=
With[{
Slides = KeyValueMap[
Function[{Key1, Value1},
<section>
<h1><Key1 /></h1>
<Value1/>
</section>
],
assoc
]
},
<Slides/>
]
.slide
<ManySlides Data={ChatReport}/>